package ddz.net;

import ch.qos.logback.classic.Logger;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLException;
import java.net.InetSocketAddress;
import java.security.cert.CertificateException;
import java.util.concurrent.Executors;

/**
 * @author zkpursuit
 */
public class WebSocketServer {

    private final Logger logger = (Logger) LoggerFactory.getLogger(WebSocketServer.class);
    protected SslContext sslCtx;

    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;
    private EventLoopGroup businessThreadGroup;
    private ChannelFuture mainChannel;

    /**
     * 构造方法
     *
     * @param SSL 是否启用https访问
     */
    public WebSocketServer(boolean SSL) {
        if (SSL) {
            try {
                SelfSignedCertificate ssc = new SelfSignedCertificate();
                sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
            } catch (CertificateException | SSLException ex) {
                logger.error(ex.getLocalizedMessage(), ex);
            }
        } else {
            sslCtx = null;
        }
    }

    /**
     * 启动服务
     *
     * @param address                绑定的服务器地址
     * @param businessThreadPoolSize 业务处理线程池线程个数，并发量大需调整此值
     */
    public void start(InetSocketAddress address, int businessThreadPoolSize) {
        this.bossGroup = new NioEventLoopGroup();
        this.workerGroup = new NioEventLoopGroup();
        this.businessThreadGroup = new NioEventLoopGroup(businessThreadPoolSize, Executors.newFixedThreadPool(businessThreadPoolSize));
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            if (sslCtx != null) {
                                ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()));
                            }
                            ch.pipeline().addLast(new HttpServerCodec());
                            ch.pipeline().addLast(new HttpObjectAggregator(65536));
                            //参数一：判定客户端网络断开的超时时间
                            //参数二：心跳间隔时间（考虑到网络延迟，此处设置比客户端心跳间隔时间多）
                            ch.pipeline().addLast(new IdleStateHandler(10, 600, 0));
                            ch.pipeline().addLast(businessThreadGroup, "business", new WebSocketServerHandler());
                        }
                    });
            this.mainChannel = serverBootstrap.bind(address).sync();
            this.mainChannel.channel().closeFuture().sync();
        } catch (InterruptedException ex) {
            logger.error(ex.getLocalizedMessage(), ex);
        } finally {
            this.bossGroup.shutdownGracefully();
            this.workerGroup.shutdownGracefully();
            this.businessThreadGroup.shutdownGracefully();
        }
    }

    /**
     * 停止netty服务
     */
    public void stop() {
        if (this.bossGroup != null) {
            this.bossGroup.shutdownGracefully();
        }
        if (this.workerGroup != null) {
            this.workerGroup.shutdownGracefully();
        }
        if (this.businessThreadGroup != null) {
            this.businessThreadGroup.shutdownGracefully();
        }
        if (this.mainChannel != null) {
            this.mainChannel.channel().close().awaitUninterruptibly();
        }
    }

}
